<?php
/**
 * Copyright (c) 2006-2007, Julien PORTALIER
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright    Copyright (c) 2006-2007, Julien PORTALIER
 * @link         http://featherphp.googlecode.com/
 * @license      http://www.opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Framework's main Model.
 */
abstract class Model
{
	public    $name;
	protected $behaviors = array();
	
	// CRUD
	protected $belongsTo           = array();
	protected $hasMany             = array();
	protected $hasOne              = array();
	protected $hasOneToMany        = array();
	protected $hasAndBelongsToMany = array();
	
	protected $_belongsTo;
	protected $_hasMany;
	protected $_hasOne;
	
	// DATA
	public    $id     = null;
	public    $data   = array();
	public    $validationErrors = array();
	protected $validate;
	
	// APP
	protected $controller;
	public    $params;
	
	public    $reset            = true;
	
	protected $defaultRecursive = 1;
	public    $_recursive;
	public    $recursive;
	public    $recursiveFields  = '*';
	
	
	function __construct(Controller $controller=null)
	{
		if (empty($this->name))
			$this->name = get_class($this);
		$this->load = Load::getInstance();
		
		// data
		if (!is_null($controller))
		{
			$this->controller =  $controller;
			$this->params     =& $controller->params;
			$this->data       =& $controller->data;
		}
		
		// associations
		if (!empty($this->hasOneToMany))
		{
			$this->hasOne  = array_merge($this->hasOne,  $this->hasOneToMany);
			$this->hasMany = array_merge($this->hasMany, $this->hasOneToMany);
		}
		if (!empty($this->hasAndBelongsToMany))
		{
			$this->belongsTo = array_merge($this->belongsTo, $this->hasAndBelongsToMany);
			$this->hasMany   = array_merge($this->hasMany,   $this->hasAndBelongsToMany);
		}
		
		// defaults
		$this->recursive  = $this->defaultRecursive;
		
		$this->_belongsTo = $this->belongsTo;
		$this->_hasMany   = $this->hasMany;
		$this->_hasOne    = $this->hasOne;
		
		// behaviors
		$this->behaviors = array_merge(Core::$behaviors, $this->behaviors);
	}
	
	protected function __get($class)
	{
		$type = (!in_array($class, $this->behaviors)) ? 'model' : 'behavior';
		$this->$class =  $this->load->$type($class);
		return $this->$class;
	}
	
	
	// dynamic associations
	
	function bind($assocs)
	{
		foreach($assocs as $type => $models)
		{
			if (!is_array($models))
				$models = explode(',', $models);
#			$this->$type      = array_merge($this->$type, $models);
			$this->{"_$type"} = array_merge($this->{"_$type"}, $models);
		}
		$this->_modifiedModels = true;
	}
	
	function unbind($assocs, $permanent=false)
	{
		foreach($assocs as $type => $models)
		{
			if ($models === '*')
				$models = $this->$type;
			elseif (!is_array($models))
				$models = explode(',', $models);
			
			foreach ($models as $model)
			{
				if ($permanent)
				{
					$key = array_search($model, $this->$type);
					if ($key !== false)
						unset($this->{$type}[$key]);
				}
				
				$key = array_search($model, $this->{"_$type"});
				if ($key !== false)
					unset($this->{"_$type"}[$key]);
			}
		}
		$this->_modifiedModels = true;
	}
	
	
	// read
	
	abstract function & findAll($fields=null, $limit=null, $page=null, $recursive=null);
	
	/**
	 * Finds a record.
	 * 
	 * @param  $fields
	 * @param  $recursive
	 * @return array
	 */
	function & find($fields='*', $recursive=null)
	{
		$rs = $this->findAll($fields, 1, null, $recursive);
		
		if (isset($rs[$this->name]))
			$rs = $rs[$this->name];
		elseif (isset($rs[0]))
			$rs = $rs[0];
		return $rs;
	}
	
	protected function __reset()
	{
		$this->recursive = $this->defaultRecursive;
	}
	
	
	// validate
	
	function validates($data=null)
	{
		require_once APP.'config'.DS.'validations.php';
		
		if (!is_null($data))
			$this->__setData(&$data);
		$valid = $this->beforeValidate();
		
		// self
		$valid = $this->__validates();
		
		// related
		if ($this->recursive)
		{
			foreach ($this->__getRelated() as $model)
			{
				if (isset($this->data[$model]))
				{
					$valid &= $this->$model->validates();
					if (!empty($this->$model->validationErrors))
					{
						$this->validationErrors = array_merge(
							$this->validationErrors,
							$this->$model->validationErrors
						);
					}
				}
			}
		}
		
		return ($this->afterValidate() && $valid);
	}
	
	protected function __validates()
	{
		if (empty($this->validate))
			return true;
		
		$errors = array();
		
		foreach($this->data[$this->name] as $field => $value)
		{
			if (empty($this->validate[$field]))
				continue;
			foreach($this->validate[$field] as $validator => $msgstr)
			{
				if (method_exists($this, $validator))
				{
					if (call_user_func_array(array(&$this, $validator), array($field, $value)))
					{
						$errors[$field] = $msgstr;
						break 1;
					}
				}
				elseif (!preg_match($validator, $value))
				{
					$errors[$field] = $msgstr;
					break 1;
				}
			}
		}
		if (!empty($errors))
			$this->validationErrors[$this->name] = $errors;
		
		return empty($errors);
	}
	
	/*protected*/ function invalidate($field, $message)
	{
		$this->validationErrors[$this->name][$field] = $message;
	}
	
	
	// create & update
	
	function save($data=null, $validate=true, $type=null)
	{
		if (!is_null($data))
			$this->__setData(&$data);
		
		// validation
		if ($validate and !$this->validates())
			return false;
		
		// self
		if (!$this->beforeSave())
			return false;
		if (!$this->__save($type))
			return false;
		
		// related
		foreach ($this->__getRelated() as $model)
		{
			if (isset($this->data[$model]))
			{
				if (!$this->$model->beforeSave())
					return false;
				if (!$this->$model->__save($type))
					return false;
				if (!$this->$model->afterSave())
					return false;
			}
		}
		
		return $this->afterSave();
	}
	
	abstract protected function __save($type=null);	
	
	
	// delete
	
	function delete($id)
	{
		if ($this->recursive)
		{
			foreach ($this->__getRelated() as $model)
			{
				$this->$model->deletionIsRecursive = true;
				if (!$this->$model->delete())
					return false;
				$this->$model->deletionIsRecursive = false;
			}
		}
		
		if (!$this->beforeDelete())
			return false;
		
		$rs = $this->__delete($id);
		
		return ($rs and $this->afterDelete());
	}
	
	abstract protected function __delete($id=null);
	
	
	// some internals
	
	protected function & __getRelated()
	{
		if (!isset($this->__related))
		{
			if (!empty($this->hasMany) or !empty($this->hasOne))
			{
				$this->__related = array_unique(array_merge($this->hasMany, $this->hasOne));
				$this->__related = array_diff($this->__related, $this->belongsTo);
			}
			else
				$this->__related = array();
		}
		return $this->__related;
	}
	
	protected function __setData($data)
	{
		if (!isset($data[$this->name]))
			$this->data[$this->name] =& $data;
		else
			$this->data =& $data;
	}
	
	
	// callbacks
	
	protected function setBoolean()
	{
		$fields = func_get_args();
		foreach($fields as $field)
			$this->data[$this->name][$field] = isset($this->data[$this->name][$field]);
	}
	
	function beforeValidate() { return true; }
	function afterValidate()  { return true; }
	
	function beforeSave() { return true; }
	function afterSave()  { return true; }
	
	function beforeDelete() { return true; }
	function afterDelete()  { return true; }
}
?>